--- /dev/null
+#! /usr/bin/perl
+# Copyright (C) 2014 Julien Moutinho <julm+ikiwiki+events&autogeree.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License,
+# or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+package IkiWiki::Plugin::events;
+
+use strict;
+use warnings;
+use IkiWiki 3.00;
+use Time::Local;
+use DateTime;
+#use Data::Dumper;
+
+sub import {
+ hook(type => "getsetup", id => "events", call => \&getsetup);
+ hook(type => "needsbuild", id => "events", call => \&needsbuild);
+ hook(type => "preprocess", id => "events", call => \&preprocess);
+ hook(type => "sessioncgi", id => "events", call => \&sessioncgi);
+ }
+sub getsetup () {
+ return
+ { plugin =>
+ { safe => 1
+ , rebuild => undef
+ , section => "widget"
+ }
+ };
+ }
+
+my $now
+ = DateTime->now
+ ( time_zone => 'local'
+ , locale => $config{locale}
+ )->set_time_zone('floating');
+my @days = ('01'..'31');
+my @hours = ('00'..'23');
+my @minutes = ('00'..'59');
+
+# update
+sub set_rendering_expiration ($$) {
+ my ($page, $timestamp) = @_;
+ if (not exists $pagestate{$page}{events}{expiration}
+ or $timestamp < $pagestate{$page}{events}{expiration}) {
+ my $time = DateTime->from_epoch
+ ( epoch => $timestamp
+ , time_zone => 'UTC'
+ , locale => $config{locale}
+ );
+ #debug("events: set_rendering_expiration(): will refresh: page=".$page
+ # . " after: date=".$time->strftime('%Y-%m-%d_%H-%M-%S'));
+ $pagestate{$page}{events}{expiration} = $timestamp;
+ }
+ }
+sub set_next_rendering (%) {
+ my %params = @_;
+ if ($params{type} eq 'month'
+ and $params{focus}->year() == $now->year()
+ and $params{focus}->month() == $now->month()) {
+ # NOTE: calendar for current month, updates next day
+ my $update = $params{focus}->clone;
+ $update->set_hour(0);
+ $update->set_minute(0);
+ $update->set_second(0);
+ $update->set_nanosecond(0);
+ my $duration = DateTime::Duration->new(days => 1, end_of_month => 'limit');
+ $update->add_duration($duration);
+ set_rendering_expiration($params{destpage}, $update->epoch());
+ #debug("events: will refresh current month: page=".$params{destpage}
+ # . " after: date=".$update->strftime('%Y-%m-%d_%H-%M-%S'));
+ }
+ elsif ($params{type} eq 'month'
+ and (( $params{focus}->year() == $now->year()
+ and $params{focus}->month() > $now->month())
+ or $params{focus}->year() > $now->year())) {
+ # NOTE: calendar for upcoming month, updates 1st of next month
+ my $update = $params{focus}->clone;
+ $update->set_day(1);
+ $update->set_hour(0);
+ $update->set_minute(0);
+ $update->set_second(0);
+ $update->set_nanosecond(0);
+ set_rendering_expiration($params{destpage}, $update->epoch());
+ #debug("events: will refresh upcoming month: page=".$params{destpage}
+ # . " after: date=".$update->strftime('%Y-%m-%d_%H-%M-%S'));
+ }
+ elsif ($params{type} eq 'day'
+ and ($params{focus}->year() == $now->year()
+ and $params{focus}->month() == $now->month()
+ and $params{focus}->day() == $now->day())) {
+ # NOTE: calendar for current day, updates next day
+ my $update = $params{focus}->clone;
+ $update->set_hour(0);
+ $update->set_minute(0);
+ $update->set_second(0);
+ $update->set_nanosecond(0);
+ my $duration = DateTime::Duration->new(days => 1, end_of_month => 'limit');
+ $update->add_duration($duration);
+ set_rendering_expiration($params{destpage}, $update->epoch());
+ #debug("events: will refresh current day: page=".$params{destpage}
+ # . " after: date=".$update->strftime('%Y-%m-%d_%H-%M-%S'));
+ }
+ elsif ($params{type} eq 'day'
+ and (( $params{focus}->year() == $now->year()
+ and ( $params{focus}->month() > $now->month()
+ or ($params{focus}->month() == $now->month()
+ and $params{focus}->day() > $now->day() ))
+ or $params{focus}->year() > $now->year()))) {
+ # NOTE: calendar for upcoming day, updates that day
+ my $update = $params{focus}->clone;
+ $update->set_hour(0);
+ $update->set_minute(0);
+ $update->set_second(0);
+ $update->set_nanosecond(0);
+ set_rendering_expiration($params{destpage}, $update->epoch());
+ #debug("events: will refresh upcoming day: page=".$params{destpage}
+ # . " after: date=".$update->strftime('%Y-%m-%d_%H-%M-%S'));
+ }
+ }
+sub needsbuild (@) {
+ my $needsbuild = shift;
+ foreach my $page (keys %pagestate) {
+ if (exists $pagestate{$page}{events}{expiration}) {
+ if ($pagestate{$page}{events}{expiration} <= $now->epoch()) {
+ # NOTE: force a rebuild so the calendar shows the current day
+ push @$needsbuild, $pagesources{$page};
+ }
+ if (exists $pagesources{$page}
+ and grep { $_ eq $pagesources{$page} } @$needsbuild) {
+ # NOTE: remove state, will be re-added
+ # if the calendar is still there during the rebuild
+ delete $pagestate{$page}{events};
+ }
+ }
+ }
+ return $needsbuild;
+ }
+
+# render
+sub date_of_page ($%) {
+ my ($page, %params) = @_;
+ my $dir = IkiWiki::dirname($page);
+ my ($year, $month, $day, $hour, $hour_begin, $hour_end)
+ = $dir =~ m{
+ .*/
+ (\d+)/
+ (01|02|03|04|05|06|07|08|09|10|11|12)/
+ (01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31)/
+ (([0-2][0-9]h[0-5][0-9])(-[0-2][0-9]h[0-5][0-9])?)?
+ $
+ }x;
+ my $r =
+ { year => $year
+ , month => $month
+ , day => $day
+ , hour => $hour
+ , hour_begin => $hour_begin
+ , hour_end => $hour_end
+ };
+ #debug("date_of_page: dir=".$dir." ".Dumper($r));
+ return $r;
+ }
+sub event_of_page ($%) {
+ my ($event, %params) = @_;
+ my $title
+ = exists $pagestate{$event}{meta}{title}
+ ? $pagestate{$event}{meta}{title}
+ : pagetitle(IkiWiki::basename($event));
+ my $hour
+ = date_of_page($event)->{hour};
+ my $link
+ = htmllink
+ ( $params{page}
+ , $params{destpage}
+ , $event
+ , linktext => $title
+ , noimageinline => 1
+ , title => $title );
+ my @tags
+ = sort {lc $a cmp lc $b}
+ (grep {
+ not defined $params{tags}
+ or pagespec_match($_, $params{tags})
+ }
+ (keys %{$IkiWiki::typedlinks{$event}{tag}}));
+ @tags
+ = map {
+ my $tag = $_;
+ my $title
+ = exists $pagestate{$tag}{meta}{title}
+ ? $pagestate{$tag}{meta}{title}
+ : pagetitle(IkiWiki::basename($tag));
+ my $link
+ = htmllink
+ ( $params{page}
+ , $params{destpage}
+ , $tag
+ , linktext => $title
+ , noimageinline => 1
+ , title => $title );
+ add_depends($params{page}, $event, deptype('content'));
+ #add_depends($params{page}, $tag, deptype('content'));
+ # XXX: much too heavy :\ and midnight refresh may fix it anyway.
+ my $class = qq{$tag};
+ $class =~ s{[^a-zA-Z0-9-]}{_}g;
+ { class => "tag tag-$class"
+ , link => $link }
+ } @tags;
+ return
+ { hour => $hour
+ , link => $link
+ , tags => \@tags };
+ }
+sub events_of_pages ($%) {
+ my ($pages, %params) = @_;
+ my @day_events = ();
+ my @hour_events = ();
+ my $pagedir = sub { IkiWiki::basename(IkiWiki::dirname(shift)) };
+ foreach my $page (@$pages) {
+ my $date = date_of_page($page);
+ if (defined $date->{hour}) {
+ push @hour_events, $page;
+ }
+ else {
+ push @day_events, $page;
+ }
+ }
+ return
+ map {event_of_page($_, %params)}
+ ( (sort {lc $pagedir->($a) cmp lc $pagedir->($b)} @day_events)
+ , (sort {lc $pagedir->($a) cmp lc $pagedir->($b)} @hour_events) );
+ }
+sub event_html ($$%) {
+ my ($date, $format, %params) = @_;
+ my $day = sprintf("%02d", $date->day());
+ my $month = sprintf("%02d", $date->month());
+ my $year_html = $date->year();
+ my $month_html
+ = $format->{month_name}
+ ? $date->month_name()
+ : $month;
+ my $day_html
+ = $format->{day_name}
+ ? $date->day_name()." ".$date->day()
+ : $format->{day_abbr}
+ ? $date->day_abbr()." ".$date->day()
+ : $date->day();
+ my $wday_class
+ = "wday-".($date->day_of_week() - 1)
+ . ( ($date->year() == $now->year()
+ and $date->month() == $now->month()
+ and $date->day() == $now->day())
+ ? " today"
+ : "" );
+ my $new_html = "";
+ if (not defined $params{new} or $params{new} ne 'no') {
+ if ($format->{year}) {
+ my $year_page
+ = sprintf
+ ( '%s/%d'
+ , $params{base}
+ , $date->year()
+ );
+ add_depends($params{page}, $year_page, deptype("presence"));
+ if ($pagesources{$year_page}) {
+ $year_html
+ = htmllink
+ ( $params{page}
+ , $params{destpage}
+ , $year_page
+ , linktext => $year_html
+ , noimageinline => 1 );
+ }
+ }
+ if ($format->{month}) {
+ my $month_page
+ = sprintf
+ ( '%s/%d/%02d'
+ , $params{base}
+ , $date->year()
+ , $date->month()
+ );
+ add_depends($params{page}, $month_page, deptype("presence"));
+ if ($pagesources{$month_page}) {
+ $month_html
+ = htmllink
+ ( $params{page}
+ , $params{destpage}
+ , $month_page
+ , linktext => $month_html
+ , noimageinline => 1 );
+ }
+ }
+ if ($format->{day}) {
+ my $day_page
+ = sprintf
+ ( '%s/%d/%02d/%02d'
+ , $params{base}
+ , $date->year()
+ , $date->month()
+ , $date->day()
+ );
+ add_depends($params{page}, $day_page, deptype("presence"));
+ if ($pagesources{$day_page}) {
+ $day_html
+ = htmllink
+ ( $params{page}
+ , $params{destpage}
+ , $day_page
+ , linktext => $day_html
+ , noimageinline => 1 );
+ }
+ }
+ unless ($params{nonew} or not $format->{new}) {
+ $new_html
+ .= qq{<a class='new' href='}
+ . IkiWiki::cgiurl
+ ( base => $params{base}
+ , ($date->day()
+ ?(day => $day):())
+ , (($date->month() or $date->day())
+ ?(month => $month):())
+ , (($date->year() or $date->month() or $date->day())
+ ?(year => $date->year()):())
+ , do => 'events'
+ , page => $params{destpage}
+ )
+ . qq{' rel='nofollow'>+</a>};
+ }
+ }
+ return
+ { new => $new_html
+ , day => $day_html
+ , month => $month_html
+ , year => $year_html
+ , wday => $wday_class
+ };
+ }
+sub preprocess_day (@) {
+ my %params = @_;
+ my @pages
+ = pagespec_match_list
+ ( $params{page}
+ , $params{pages}
+ , deptype => deptype("presence")
+ # NOTE: add presence dependencies to update calendar when pages are added/removed
+ );
+ my $event_html
+ = event_html
+ ( $params{focus}
+ , {day=>1, day_name=>1, new => 1}
+ , %params );
+ my @events
+ = map {
+ my @tags
+ = map {"<li\n class='".$_->{class}."'>".$_->{link}."</li>"}
+ @{$_->{tags}};
+ "<ul\n class='events'><li class='event'>"
+ . "<span class='head'>"
+ . (defined $_->{hour} ? "<span class='hour'>$_->{hour}</span>" : "")
+ . "<span class='link'>$_->{link}</span>"
+ . "</span>"
+ . "<ul\n class='tags'>".join("", @tags)."</ul>"
+ . "</li></ul>"
+ }
+ events_of_pages(\@pages, %params);
+ return
+ "<table\n class='wday'>"
+ . "<thead><tr><th><span\n class='head'><span\n class='day'>"
+ . $event_html->{day}
+ . $event_html->{new}
+ . "</span></span></th></tr></thead>"
+ . "<tbody><tr><td\n class='wday $event_html->{wday}'>"
+ . join("", @events)
+ . "</td></tr></tbody>"
+ . "</table>";
+ }
+sub preprocess_month (@) {
+ my %params = @_;
+ my $one_day = DateTime::Duration->new(days => 1, end_of_month => 'limit');
+ my $day = $params{focus}->clone->set_day(1);
+ my $last_day
+ = DateTime->last_day_of_month
+ ( year => $params{focus}->year()
+ , month => $params{focus}->month() )->day();
+
+ my @pages
+ = pagespec_match_list
+ ( $params{page}
+ , $params{pages}
+ , deptype => deptype("presence")
+ # NOTE: add presence dependencies to update calendar when pages are added/removed
+ );
+
+ # NOTE: sort the pages by days of the month
+ my %days = map {($_=>[])} (1 .. $last_day);
+ foreach my $page (@pages) {
+ my $page_ctime = DateTime->from_epoch
+ ( epoch => $IkiWiki::pagectime{$page}
+ , time_zone => 'local'
+ , locale => $config{locale}
+ );
+ push @{$days{$page_ctime->day()}}, $page;
+ }
+
+ my $t='<tr>';
+ my $first_wday = $day->clone();
+ my $last_wday = ($params{week_start_day} + 6) % 7;
+ while ($first_wday->day_of_week() - 1 != $params{week_start_day}) {
+ # NOTE: pad the begining
+ $first_wday->subtract_duration($one_day);
+ $t.="<td class='no-wday'></td>";
+ }
+ my $month = $day->month();
+ for (; $day->month() == $month; $day->add_duration($one_day)) {
+ my $event_html
+ = event_html
+ ( $day
+ , {day=>1, day_abbr=>1, new => 1}
+ , %params );
+ $t.= "<td class='wday $event_html->{wday}'>";
+ my @events
+ = map {
+ my @tags
+ = map {"<li\n class='".$_->{class}."'>".$_->{link}."</li>"}
+ @{$_->{tags}};
+ "<li\n class='event'>"
+ . "<span class='head'>"
+ . (defined $_->{hour} ? "<span class='hour'>$_->{hour}</span>" : "")
+ . "<span class='link'>$_->{link}</span>"
+ . "</span>"
+ . "<ul class='tags'>".join("", @tags)."</ul>"
+ . "</li>\n"
+ }
+ events_of_pages($days{$day->day()}, %params);
+ $t .=
+ "<span class='head'>"
+ . "<span class='day'>"
+ . $event_html->{day}
+ . $event_html->{new}
+ . "</span>"
+ . "</span>"
+ . "<ul class='events'>".join("", @events)."</ul>";
+ $t.='</td>';
+ if ($day->day_of_week() - 1 == $last_wday) {
+ $t.="</tr>";
+ $t.="<tr>"
+ if ($day->day_of_month() < $last_day);
+ }
+ }
+ while ($day->day_of_week() - 1 != $params{week_start_day}) {
+ # NOTE: pad the end
+ $day->add_duration($one_day);
+ $t.="<td class='no-wday'></td>";
+ }
+ $t.='</tr>';
+ my $event_html
+ = event_html
+ ( $params{focus}
+ , {year=>1, month=>1, month_name=>1}
+ , %params );
+ return
+ "<table class='month'>"
+ . "<thead>"
+ . "<tr>"
+ . "<th colspan='7'>"
+ . "<span class='month'>$event_html->{month}</span>"
+ . " <span class='year'>$event_html->{year}</span>"
+ . "</th>"
+ . "</tr>"
+ . "<tr>"
+ . join ("", map {
+ $_ = "<th><span>".$first_wday->day_name()."</span></th>";
+ $first_wday->add_duration($one_day);
+ $_ } (1..7))
+ . "</tr>"
+ . "</thead>"
+ . "<tbody>$t</tbody></table>";
+ }
+sub preprocess (@) {
+ my %params = @_;
+ $params{focus} = $now->clone;
+
+ $params{pages} = "*" unless defined $params{pages};
+ $params{type} = "month" unless defined $params{type};
+ $params{week_start_day} = 0 unless defined $params{week_start_day};
+ $params{week_start_day} = $params{week_start_day} % 7;
+
+ unless (defined $params{base}) {
+ $params{base}
+ = defined $config{events_base}
+ ? $config{events_base}
+ : gettext('Agenda');
+ }
+ if (defined $params{day}) {
+ if ($params{day} =~ m/^([+-])(\d+)$/) {
+ my ($sign, $days) = ($1, $2);
+ my $duration = DateTime::Duration->new(days => $days, end_of_month => 'limit');
+ $params{focus}
+ = $sign eq '+'
+ ? $params{focus}->add_duration($duration)
+ : $params{focus}->subtract_duration($duration);
+ }
+ else {
+ $params{focus}->set(day => $params{day});
+ }
+ }
+ else {
+ #$params{focus}->set(day => 1);
+ }
+ if (defined $params{month}) {
+ if ($params{month} =~ m/^([+-])(\d+)$/) {
+ my ($sign, $months) = ($1, $2);
+ my $duration = DateTime::Duration->new(months => $months, end_of_month => 'limit');
+ $params{focus}
+ = $sign eq '+'
+ ? $params{focus}->add_duration($duration)
+ : $params{focus}->subtract_duration($duration);
+ }
+ else {
+ $params{focus}->set(month => $params{month});
+ }
+ }
+ if (defined $params{year}) {
+ if ($params{year} =~ m/^([+-])(\d+)$/) {
+ my ($sign, $years) = ($1, $2);
+ my $duration = DateTime::Duration->new(years => $years, end_of_month => 'limit');
+ $params{focus}
+ = $sign eq '+'
+ ? $params{focus}->add_duration($duration)
+ : $params{focus}->subtract_duration($duration);
+ }
+ else {
+ $params{focus}->set(year => $params{year});
+ }
+ }
+
+ #debug("events: focus=".$params{focus}->strftime('%Y-%m-%d_%H-%M-%S'));
+ $params{pages} =~ s[%Y][$params{focus}->year()]eg;
+ $params{pages} =~ s[%m][sprintf('%02d', $params{focus}->month())]eg;
+ $params{pages} =~ s[%d][sprintf('%02d', $params{focus}->day())]eg;
+
+ set_next_rendering(%params);
+
+ my $calendar = "";
+ if ($params{type} eq 'month') {
+ $calendar = preprocess_month(%params);
+ }
+ elsif ($params{type} eq 'day') {
+ $calendar = preprocess_day(%params);
+ }
+
+ return "<div class='calendar'>$calendar</div>";
+ }
+
+# new
+sub tmpl ($$) {
+ my ($base, $model) = @_;
+ my $page = IkiWiki::dirname($base).'/'.'templates/'.$model;
+ my $file = defined srcfile($page, 1) ? '/'.$page : $model;
+ return template($file);
+ }
+sub date_of_form ($$;%) {
+ my ($form, $prefix, %default) = @_;
+ %default =
+ ( year => 0
+ , month => 1
+ , day => 1
+ , hour => 0
+ , minute => 0
+ , %default
+ );
+ my $date;
+ eval { $date = DateTime->new
+ ( year => ($form->field($prefix.'_year') ne '' ? $form->field($prefix.'_year') : $default{year})
+ , month => ($form->field($prefix.'_month') ne '' ? substr($form->field($prefix.'_month'), 0, 2) : $default{month})
+ , day => ($form->field($prefix.'_day') ne '' ? $form->field($prefix.'_day') : $default{day})
+ , hour => ($form->field($prefix.'_hour') ne '' ? $form->field($prefix.'_hour') : $default{hour})
+ , minute => ($form->field($prefix.'_minute') ne '' ? $form->field($prefix.'_minute') : $default{minute})
+ , second => 0
+ , nanosecond => 0
+ , time_zone => 'local'
+ , locale => $config{locale}
+ )->set_time_zone('floating') };
+ return $date;
+ };
+sub duration_of_form ($$) {
+ my ($form, $prefix) = @_;
+ my $dur;
+ eval { $dur = DateTime::Duration->new
+ ( years => $form->field($prefix.'_year')
+ , months => $form->field($prefix.'_month')
+ , days => $form->field($prefix.'_day')
+ , weeks => $form->field($prefix.'_week')
+ , hours => $form->field($prefix.'_hour')
+ , minutes => $form->field($prefix.'_minute')
+ , seconds => 0
+ , nanoseconds => 0
+ , end_of_month => 'limit'
+ ) };
+ return $dur;
+ };
+sub page_of_event ($$$$$) {
+ my ($form, $from_date, $to_date, $name, $base) = @_;
+ my $time = '';
+ if ($form->field('from_hour') ne ''
+ or $form->field('from_minute') ne '') {
+ if ($from_date->hour() == $to_date->hour()
+ and $from_date->minute() == $to_date->minute()) {
+ $time = sprintf('%02dh%02d', $from_date->hour(), $from_date->minute());
+ }
+ else {
+ $time = sprintf('%02dh%02d-%02dh%02d'
+ , $from_date->hour(), $from_date->minute()
+ , $to_date->hour(), $to_date->minute());
+ }
+ }
+ return
+ ( $base
+ . ($base?'/':'').$from_date->year()
+ . '/'.sprintf('%02d', $from_date->month())
+ . '/'.sprintf('%02d', $from_date->day())
+ . '/'. ($time ne '' ? $time . '/' : '')
+ . $name
+ );
+ }
+sub check_cannewevent ($$$$) {
+ my $dest=shift;
+ my $destfile=shift;
+ my $cgi=shift;
+ my $session=shift;
+
+ # Must be a legal filename.
+ if (IkiWiki::file_pruned($destfile)) {
+ error(sprintf(gettext("illegal name")));
+ }
+ # Must not be a known source file.
+ if (exists $pagesources{$dest}) {
+ error(sprintf(gettext("%s already exists"),
+ htmllink("", "", $dest
+ , linktext => $dest
+ , noimageinline => 1)));
+ }
+ # Must not exist on disk already.
+ if (-l "$config{srcdir}/$destfile" || -e _) {
+ error(sprintf(gettext("%s already exists on disk"), $destfile));
+ }
+
+ # Must be editable.
+ IkiWiki::check_canedit($dest, $cgi, $session);
+
+ my $can_newevent;
+ IkiWiki::run_hooks(can_newevent => sub {
+ return if defined $can_newevent;
+ my $ret=shift->(cgi => $cgi, session => $session, dest => $dest, destfile => $destfile);
+ if (defined $ret) {
+ if ($ret eq "") {
+ $can_newevent=1;
+ }
+ elsif (ref $ret eq 'CODE') {
+ $ret->();
+ $can_newevent=0;
+ }
+ elsif (defined $ret) {
+ error($ret);
+ $can_newevent=0;
+ }
+ }
+ });
+ return defined $can_newevent ? $can_newevent : 1;
+ }
+sub post_newevent ($$$) {
+ my $cgi=shift;
+ my $session=shift;
+ my $dest=shift;
+
+ IkiWiki::redirect($cgi, urlto($dest));
+ exit;
+ }
+sub newevent_hook {
+ my %params = @_;
+ my @events = @{$params{events}};
+ my %done = %{$params{done}};
+ my $cgi = $params{cgi};
+ my $session = $params{session};
+ return ()
+ unless @events;
+ my @next;
+ foreach my $event (@events) {
+ unless (exists $done{$event->{page}} && $done{$event->{file}}) {
+ IkiWiki::run_hooks(newevent => sub {
+ push @next, shift->
+ ( cgi => $cgi
+ , event => $event
+ , session => $session
+ );
+ });
+ $done{$event->{page}} = 1;
+ }
+ }
+ push @events, newevent_hook
+ ( cgi => $cgi
+ , done => \%done
+ , events => \@next
+ , session => $session
+ );
+ my %seen; # NOTE: insure unicity
+ return grep { ! $seen{$_->{page}}++ } @events;
+ }
+sub preview($$$$$) {
+ my ($cgi, $session, $form, $events, $months) = @_;
+ $form->tmpl_param(year => gettext("year"));
+ $form->tmpl_param(month => gettext("month"));
+ $form->tmpl_param(day => gettext("day"));
+ $form->tmpl_param(hour => gettext("hour"));
+ $form->tmpl_param(min => gettext("min"));
+ $form->tmpl_param(dow => gettext("day of week"));
+ $form->tmpl_param(page => gettext("page"));
+ $form->tmpl_param(events => [
+ map {
+ { from_year => $_->{from}->year()
+ , from_month => sprintf('%02d', $_->{from}->month())
+ , from_monthname => $months->{$_->{from}->month()}
+ , from_day => sprintf('%02d', $_->{from}->day())
+ , from_hour => sprintf('%02d', $_->{from}->hour())
+ , from_minute => sprintf('%02d', $_->{from}->minute())
+ , from_dow => $_->{from}->dow()
+ , from_downame => $_->{from}->day_name()
+ , to_year => $_->{to}->year()
+ , to_month => sprintf('%02d', $_->{to}->month())
+ , to_monthname => $months->{$_->{to}->month()}
+ , to_day => sprintf('%02d', $_->{to}->day())
+ , to_hour => sprintf('%02d', $_->{to}->hour())
+ , to_minute => sprintf('%02d', $_->{to}->minute())
+ , to_dow => $_->{to}->dow()
+ , page =>
+ htmllink("", "", $_->{page}
+ , linktext => $_->{page}
+ , noimageinline => 1)
+ }
+ } @$events
+ ]);
+ if (@$events > 0) {
+ my $page = @$events[0];
+ # FROM: editpage.pm
+ my $new = not exists $pagesources{$page};
+ # temporarily record its type
+ my $type = $config{default_pageext};
+ $pagesources{$page} = $page.".".$type if $new;
+ my %wasrendered = map { $_ => 1 } @{$renderedfiles{$page}};
+ my $content = @$events[0]->{content};
+
+ IkiWiki::run_hooks(editcontent => sub {
+ $content = shift->
+ ( cgi => $cgi
+ , content => $content
+ , page => $page
+ , session => $session
+ );
+ });
+ my $preview = IkiWiki::htmlize($page, $page, $type,
+ IkiWiki::linkify($page, $page,
+ IkiWiki::preprocess($page, $page,
+ IkiWiki::filter($page, $page, $content), 0, 1)));
+ IkiWiki::run_hooks(format => sub {
+ $preview = shift->
+ ( content => $preview
+ , page => $page
+ );
+ });
+ $form->tmpl_param("preview", $preview);
+
+ # Previewing may have created files on disk.
+ # Keep a list of these to be deleted later.
+ my %previews = map { $_ => 1 } @{$wikistate{editpage}{previews}};
+ foreach my $f (@{$renderedfiles{$page}}) {
+ $previews{$f} = 1 unless $wasrendered{$f};
+ }
+
+ # Throw out any other state changes made during previewing,
+ # and save the previews list.
+ IkiWiki::loadindex();
+ @{$wikistate{editpage}{previews}} = keys %previews;
+ IkiWiki::saveindex();
+ }
+ else {
+ $form->tmpl_param("preview", gettext("No event"));
+ }
+ }
+sub create ($$$$$) {
+ my ($event, $cgi, $session, $months, $base) = @_;
+ check_cannewevent
+ ( $event->{page}
+ , $event->{file}
+ , $cgi
+ , $session
+ );
+ my $pageext = $config{default_pageext};
+
+ $config{cgi} = 0; # NOTE: avoid CGI error message
+ eval { writefile($event->{file}, $config{srcdir}, $event->{content}) };
+ if ($config{rcs}) {
+ IkiWiki::rcs_add($event->{file});
+ }
+ # month page
+ my $monthpage =
+ ( $base
+ . ($base?'/':'').$event->{from}->year()
+ . '/'.sprintf('%02d', $event->{from}->month())
+ );
+ my $monthfile = IkiWiki::newpagefile($monthpage, $pageext);
+ if (not exists $pagesources{$monthpage}
+ and not -l $config{srcdir}.'/'.$monthfile
+ and not -e _) {
+ my $tmpl_neweventmonth = tmpl($base, 'neweventmonth.tmpl');
+ $tmpl_neweventmonth->param(base => $base);
+ $tmpl_neweventmonth->param(year => $event->{from}->year());
+ $tmpl_neweventmonth->param(month => sprintf('%02d', $event->{from}->month()));
+ $tmpl_neweventmonth->param(monthname => $months->{$event->{from}->month()});
+ my $content = $tmpl_neweventmonth->output();
+ eval { writefile($monthfile, $config{srcdir}, $content) };
+ if ($config{rcs}) {
+ IkiWiki::rcs_add($monthfile);
+ }
+ }
+ # day page
+ my $daypage =
+ ( $monthpage
+ . '/'.sprintf('%02d', $event->{from}->day())
+ );
+ my $dayfile = IkiWiki::newpagefile($daypage, $pageext);
+ if (not exists $pagesources{$daypage}
+ and not -l $config{srcdir}.'/'.$dayfile
+ and not -e _) {
+ my $tmpl_neweventday = tmpl($base, 'neweventday.tmpl');
+ $tmpl_neweventday->param(base => $base);
+ $tmpl_neweventday->param(year => $event->{from}->year());
+ $tmpl_neweventday->param(month => sprintf('%02d', $event->{from}->month()));
+ $tmpl_neweventday->param(monthname => $months->{$event->{from}->month()});
+ $tmpl_neweventday->param(day => sprintf('%02d', $event->{from}->day()));
+ $tmpl_neweventday->param(dayname => $event->{from}->day_name());
+ my $content = $tmpl_neweventday->output();
+ eval { writefile($dayfile, $config{srcdir}, $content) };
+ if ($config{rcs}) {
+ IkiWiki::rcs_add($dayfile);
+ }
+ }
+ $config{cgi} = 1;
+ }
+sub sessioncgi ($$) {
+ my ($cgi, $session) = @_;
+ if (defined $cgi->param('do') && $cgi->param('do') eq "newevent") {
+ # TOTRY: decode_cgi_utf8($cgi);
+ my $base = Encode::decode_utf8(URI::Escape::uri_unescape(IkiWiki::possibly_foolish_untaint($cgi->param('base'))));
+ &IkiWiki::check_canedit($base, $cgi, $session);
+ my $page = Encode::decode_utf8(URI::Escape::uri_unescape(IkiWiki::possibly_foolish_untaint($cgi->param('page'))));
+
+ my $now_date = DateTime->now
+ ( time_zone => 'local'
+ , locale => $config{locale}
+ )->set_time_zone('floating');
+ my %dows = map { ($_ => $now_date->{locale}->day_format_wide->[ $_ ]) } (0..6);
+ my %months = map { ($_ => $now_date->{locale}->month_format_wide->[ $_ - 1 ]) } (1..12);
+ my $cgi_date;
+ eval { $cgi_date = DateTime->new
+ ( year => defined $cgi->param("year") ? $cgi->param("year") : $now_date->year()
+ , month => defined $cgi->param("month") ? $cgi->param("month") : $now_date->month()
+ , day => defined $cgi->param("day") ? $cgi->param("day") : $now_date->day()
+ , hour => defined $cgi->param("hour") ? $cgi->param("hour") : $now_date->hour()
+ , minute => defined $cgi->param("minute") ? $cgi->param("minute") : $now_date->minute()
+ , second => 0
+ , nanosecond => 0
+ , time_zone => 'local'
+ , locale => $config{locale}
+ )->set_time_zone('floating') };
+ error(sprintf(gettext("illegal date")))
+ unless $cgi_date;
+
+ my @years = ($cgi_date->year() .. $cgi_date->year()+5);
+ my $week_start_day
+ = (defined $config{week_start_day} and $config{week_start_day} >= 0 and $config{week_start_day} <= 6)
+ ? $config{week_start_day}
+ : 1;
+ my @dow_order = ($week_start_day .. 6, 0 .. $week_start_day-1);
+
+ my $tags = $typedlinks{$page}{tag};
+ my $buttons = [qw{Preview Create}];
+ my ($from_date, $to_date, $end_date, $inc_dur);
+ my $form = CGI::FormBuilder->new
+ ( action => IkiWiki::cgiurl()
+ , charset => "utf-8"
+ , fields => [qw{
+ do base
+ from_date from_year from_month from_day from_hour from_minute
+ to_date to_year to_month to_day to_hour to_minute
+ inc_dur inc_year inc_month inc_week inc_day inc_hour inc_minute
+ end_times end_date end_year end_month end_day end_hour end_minute
+ dom name content
+ }]
+ , header => 0
+ , javascript => 0
+ , messages =>
+ {
+ # form_required_text => 'form_required_text'
+ # , form_invalid_text => 'form_invalid_text'
+ # , form_invalid_file => 'form_invalid_file'
+ # , form_invalid_input => gettext('allowed characters: ').$config{wiki_file_chars}
+ form_invalid_select => gettext('invalid selection')
+ }
+ , method => 'POST'
+ , name => "newevent"
+ , stylesheet => 1
+ , params => $cgi
+ , required => [qw{do base year month day name from_date to_date end_date inc_dur}]
+ , submit => [qw{Preview Create}]
+ , title => gettext("newevent")
+ , template => { template("newevent.tmpl") }
+ , validate =>
+ { from_date => { perl => sub {
+ my (undef, $form) = @_;
+ $from_date = date_of_form($form, 'from')
+ unless defined $from_date;
+ defined $from_date
+ } }
+ , to_date => { perl => sub {
+ my (undef, $form) = @_;
+ $from_date = date_of_form($form, 'from')
+ unless defined $from_date;
+ if (defined $from_date) {
+ $to_date = date_of_form($form, 'to'
+ , year => $from_date->year()
+ , month => $from_date->month()
+ , day => $from_date->day()
+ , hour => $from_date->hour()
+ , minute => $from_date->minute());
+ defined $to_date
+ and (DateTime->compare($from_date, $to_date) <= 0)
+ }
+ else {return 0;}
+ } }
+ , end_date => { perl => sub {
+ my (undef, $form) = @_;
+ if ( $form->field('end_year') ne ''
+ or $form->field('end_month') ne ''
+ or $form->field('end_day') ne '' ) {
+ $from_date = date_of_form($form, 'from')
+ unless defined $from_date;
+ if (defined $from_date) {
+ $end_date = date_of_form($form, 'end'
+ , year => $from_date->year()
+ , month => $from_date->month()
+ , day => $from_date->day()
+ , hour => $from_date->hour()
+ , minute => $from_date->minute());
+ (defined $from_date and defined $end_date
+ and DateTime->compare($from_date, $end_date) <= 0)
+ }
+ else {return 0;}
+ }
+ else {
+ 1;
+ }
+ } }
+ , name => '/^.+$/'
+ , base => '/^.*$/'
+ , end_times => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
+ , inc_year => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
+ , inc_month => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
+ , inc_week => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
+ , inc_day => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
+ , inc_hour => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
+ , inc_minute => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
+ , inc_dur => sub {
+ my (undef, $form) = @_;
+ $inc_dur = duration_of_form($form, 'inc');
+ defined $inc_dur
+ and ($inc_dur->is_positive() or $inc_dur->is_zero());
+ }
+ }
+ );
+ $base = $form->field('base') ? $form->field('base') : $base;
+ $form->title(sprintf(gettext("creating new events"), pagetitle(IkiWiki::basename($page))));
+ $form->field(name => "do", type => "hidden", value => 'newevent', force => 1);
+ $form->field(name => "base", type => "hidden", force => 1 , value => $base);
+ $form->field(name => "from_date", type => "hidden", value => '1', force => 1);
+ $form->field(name => "to_date", type => "hidden", value => '1', force => 1);
+ $form->field(name => "end_date", type => "hidden", value => '1', force => 1);
+ $form->field(name => "inc_dur", type => "hidden", value => '1', force => 1);
+ $form->field(name => "from_year", type => 'select', value => $cgi_date->year(), options => \@years);
+ $form->field(name => "from_month", type => 'select'
+ , value => sprintf("%02d", $cgi_date->month()).' - '.$months{$cgi_date->month()}
+ , options => [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
+ $form->field(name => "from_day", type => 'select'
+ , value => sprintf("%02d", $cgi_date->day())
+ , options => \@days);
+ $form->field(name => "from_hour", type => 'select', value => '', options => \@hours);
+ $form->field(name => "from_minute", type => 'select', value => '', options => \@minutes);
+ $form->field(name => "name", type => 'text', size => 60, value => gettext('New event'));
+ $form->field(name => "to_year", type => 'select', value => '', options => \@years);
+ $form->field(name => "to_month", type => 'select'
+ , value => ''
+ , options => [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
+ $form->field(name => "to_day", type => 'select'
+ , value => ''
+ , options => \@days);
+ $form->field(name => "to_hour", type => 'select', value => '', options => \@hours);
+ $form->field(name => "to_minute", type => 'select', value => '', options => \@minutes);
+ $form->field(name => "end_year", type => 'select', value => '', options => \@years);
+ $form->field(name => "end_month", type => 'select', value => ''
+ , options => [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
+ $form->field(name => "end_day", type => 'select', value => '', options => \@days);
+ $form->field(name => "end_hour", type => 'select', value => '', options => \@hours);
+ $form->field(name => "end_minute", type => 'select', value => '', options => \@minutes);
+ $form->field(name => "end_times", type => 'text', value => '0', size => 2);
+ $form->field(name => "inc_year", type => 'text', value => '0', size => 2);
+ $form->field(name => "inc_month", type => 'text', value => '0', size => 2);
+ $form->field(name => "inc_week", type => 'text', value => '0', size => 2);
+ $form->field(name => "inc_day", type => 'text', value => '0', size => 2);
+ $form->field(name => "inc_hour", type => 'text', value => '0', size => 2);
+ $form->field(name => "inc_minute", type => 'text', value => '0', size => 2);
+ my $tmpl_neweventcontent = tmpl($base, 'neweventcontent.tmpl');
+ $tmpl_neweventcontent->param(title => gettext('Title of the event'));
+ $tmpl_neweventcontent->param(tags => [map {{name => $_}} (sort keys %$tags)]);
+ $form->field(name => "content", type => "textarea", size => 30, rows => 20, cols => 80
+ , value => $tmpl_neweventcontent->output());
+ $form->field(name => "dom", type => 'select', multiple => 1, size => 35
+ , options => [map { my $n = $_; map {($n.' '.$dows{$_})} (0..6)} ('1°', '2°', '3°', '4°', '5°')]);
+
+ IkiWiki::decode_form_utf8($form);
+ IkiWiki::run_hooks(formbuilder_setup => sub {
+ shift->(form => $form, cgi => $cgi, session => $session, buttons => $buttons);
+ });
+ IkiWiki::decode_form_utf8($form);
+
+ if (($form->submitted eq 'Create' || $form->submitted eq 'Preview') && $form->validate) {
+ #IkiWiki::checksessionexpiry($cgi, $session, $cgi->param('sid'));
+ $base
+ = $form->field('base')
+ ? $form->field('base')
+ : (defined $config{base} ? $config{base} : gettext('Agenda'));
+ my $end_times
+ = $form->field('end_times') == 0
+ ? undef : $form->field('end_times');
+ my $dom;
+ foreach ($form->field('dom')) {
+ $dom = {} if not defined $dom;
+ $dom->{$_} = 1;
+ }
+ my $name = $form->field('name');
+ $name = IkiWiki::possibly_foolish_untaint(IkiWiki::titlepage($name));
+ # NOTE: these untaints are safe because of the checks
+ # performed in check_cannewevent later.
+ my $content = $form->field('content');
+ $content =~ s/\r\n/\n/gs;
+ $content =~ s/\n$//s;
+
+ # Queue of event creations to perfom.
+ my @events = ();
+ my $events_try = 0;
+ my $events_max
+ = defined $config{newevent_max_per_commit}
+ ? $config{newevent_max_per_commit} : (2 * 365) ;
+ my $pageext = $config{default_pageext};
+ while (++$events_try <= $events_max
+ and (not defined $end_times or --$end_times >= 0)
+ and (not defined $end_date or DateTime->compare($from_date, $end_date) <= 0)) {
+ my $dest = page_of_event($form, $from_date, $to_date, $name, $base);
+ my $week = $from_date->weekday_of_month();
+ my $day = $now_date->{locale}->day_format_wide->[$from_date->day_of_week()-1];
+ if (not defined $dom or exists $dom->{"$week° $day"}) {
+ push @events,
+ { page => $dest
+ , file => IkiWiki::newpagefile($dest, $pageext)
+ , from => $from_date
+ , to => $to_date
+ , name => $name
+ };
+ }
+ last unless defined $inc_dur and $inc_dur->is_positive();
+ $from_date = $from_date->clone->add_duration($inc_dur);
+ $to_date = $to_date->clone->add_duration($inc_dur);
+ }
+ error("events try per commit overflow: $events_max")
+ unless $events_try <= $events_max;
+ my $tmpl_neweventpage = tmpl($base, 'neweventpage.tmpl');
+ my $i = 0;
+ foreach (@events) {
+ $tmpl_neweventpage->clear_params();
+ $tmpl_neweventpage->param(content => $content);
+ $tmpl_neweventpage->param(page => $_->{page});
+ $tmpl_neweventpage->param(event => $i);
+ $tmpl_neweventpage->param("event_first" => 1)
+ if $i == 0;
+ $tmpl_neweventpage->param("event_last" => 1)
+ if $i == @events - 1;
+ $tmpl_neweventpage->param(events => \@events);
+ $tmpl_neweventpage->param(from_date => "$_->{from}");
+ $tmpl_neweventpage->param(name => $_->{name});
+ $tmpl_neweventpage->param(to_date => "$_->{to}");
+ $_->{content} = $tmpl_neweventpage->output();
+ $i++;
+ }
+ if ($form->submitted eq 'Create') {
+ @events = newevent_hook
+ ( cgi => $cgi
+ , done => {}
+ , events => \@events
+ , session => $session
+ );
+ require IkiWiki::Render;
+ if ($config{rcs}) {
+ IkiWiki::disable_commit_hook()
+ }
+ foreach my $event (@events) {
+ create($event, $cgi, $session, \%months, $base);
+ }
+ if ($config{rcs}) {
+ IkiWiki::rcs_commit_staged
+ ( message => sprintf(gettext("new event"))
+ , session => $session );
+ IkiWiki::enable_commit_hook();
+ IkiWiki::rcs_update();
+ }
+ IkiWiki::refresh();
+ IkiWiki::saveindex();
+
+ post_newevent($cgi, $session, (defined $events[0] ? $events[0]->{page} : ''));
+ }
+ elsif ($form->submitted eq 'Preview') {
+ preview($cgi, $session, $form, \@events, \%months);
+ IkiWiki::showform($form, $buttons, $session, $cgi);
+ }
+ }
+ else {
+ IkiWiki::showform($form, $buttons, $session, $cgi);
+ }
+
+ exit 0;
+ }
+ }
+
+1;
+++ /dev/null
-#!/usr/bin/perl
-package IkiWiki::Plugin::newevent;
-
-use strict;
-use warnings;
-use IkiWiki 3.00;
-use Time::Local;
-use DateTime;
-use CGI::FormBuilder;
-use Data::Dumper;
-
-sub import {
- #hook(type => "formbuilder", id => "newevent", call => \&formbuilder);
- #hook(type => "formbuilder_setup", id => "newevent", call => \&formbuilder_setup);
- hook(type => "getsetup", id => "newevent", call => \&getsetup);
- #hook(type => "preprocess", id => "newevent", call => \&preprocess);
- #hook(type => "refresh", id => "newevent", call => \&refresh);
- hook(type => "sessioncgi", id => "newevent", call => \&sessioncgi);
- }
-
-my @days = ('01'..'31');
-my @hours = ('00'..'23');
-my @minutes = ('00'..'59');
-
-sub getsetup () {
- return
- ( plugin =>
- { safe => 1
- , rebuild => undef
- , section => "misc"
- }
- , base =>
- { type => "string"
- , example => "Agenda"
- , description => "prefix of the agenda hierarchy"
- , safe => 1
- , rebuild => 1
- }
- );
- }
-sub tmpl ($$) {
- my ($base, $model) = @_;
- my $page = IkiWiki::dirname($base).'/'.'templates/'.$model;
- my $file = defined srcfile($page, 1) ? '/'.$page : $model;
- return template($file);
- }
-sub date_of_form ($$;%) {
- my ($form, $prefix, %default) = @_;
- %default =
- ( year => 0
- , month => 1
- , day => 1
- , hour => 0
- , minute => 0
- , %default
- );
- my $date;
- eval { $date = DateTime->new
- ( year => ($form->field($prefix.'_year') ne '' ? $form->field($prefix.'_year') : $default{year})
- , month => ($form->field($prefix.'_month') ne '' ? substr($form->field($prefix.'_month'), 0, 2) : $default{month})
- , day => ($form->field($prefix.'_day') ne '' ? $form->field($prefix.'_day') : $default{day})
- , hour => ($form->field($prefix.'_hour') ne '' ? $form->field($prefix.'_hour') : $default{hour})
- , minute => ($form->field($prefix.'_minute') ne '' ? $form->field($prefix.'_minute') : $default{minute})
- , second => 0
- , nanosecond => 0
- , time_zone => 'local'
- , locale => $config{locale}
- )->set_time_zone('floating') };
- return $date;
- };
-sub duration_of_form ($$) {
- my ($form, $prefix) = @_;
- my $dur;
- eval { $dur = DateTime::Duration->new
- ( years => $form->field($prefix.'_year')
- , months => $form->field($prefix.'_month')
- , days => $form->field($prefix.'_day')
- , weeks => $form->field($prefix.'_week')
- , hours => $form->field($prefix.'_hour')
- , minutes => $form->field($prefix.'_minute')
- , seconds => 0
- , nanoseconds => 0
- , end_of_month => 'limit'
- ) };
- return $dur;
- };
-sub page_of_event ($$$$$) {
- my ($form, $from_date, $to_date, $name, $base) = @_;
- my $time = '';
- if ($form->field('from_hour') ne '' or $form->field('from_minute') ne '') {
- if ($from_date->hour() == $to_date->hour()
- and $from_date->minute() == $to_date->minute()) {
- $time = sprintf('%02dh%02d', $from_date->hour(), $from_date->minute());
- }
- else {
- $time = sprintf('%02dh%02d-%02dh%02d'
- , $from_date->hour(), $from_date->minute()
- , $to_date->hour(), $to_date->minute());
- }
- }
- return
- ( $base
- . ($base?'/':'').$from_date->year()
- . '/'.sprintf('%02d', $from_date->month())
- . '/'.sprintf('%02d', $from_date->day())
- . '/'. ($time ne '' ? $time . '/' : '')
- . $name
- );
- }
-sub check_cannewevent ($$$$) {
- my $dest=shift;
- my $destfile=shift;
- my $cgi=shift;
- my $session=shift;
-
- # Must be a legal filename.
- if (IkiWiki::file_pruned($destfile)) {
- error(sprintf(gettext("illegal name")));
- }
- # Must not be a known source file.
- if (exists $pagesources{$dest}) {
- error(sprintf(gettext("%s already exists"),
- htmllink("", "", $dest
- , linktext => $dest
- , noimageinline => 1)));
- }
- # Must not exist on disk already.
- if (-l "$config{srcdir}/$destfile" || -e _) {
- error(sprintf(gettext("%s already exists on disk"), $destfile));
- }
-
- # Must be editable.
- IkiWiki::check_canedit($dest, $cgi, $session);
-
- my $can_newevent;
- IkiWiki::run_hooks(can_newevent => sub {
- return if defined $can_newevent;
- my $ret=shift->(cgi => $cgi, session => $session, dest => $dest, destfile => $destfile);
- if (defined $ret) {
- if ($ret eq "") {
- $can_newevent=1;
- }
- elsif (ref $ret eq 'CODE') {
- $ret->();
- $can_newevent=0;
- }
- elsif (defined $ret) {
- error($ret);
- $can_newevent=0;
- }
- }
- });
- return defined $can_newevent ? $can_newevent : 1;
- }
-sub post_newevent ($$$) {
- my $cgi=shift;
- my $session=shift;
- my $dest=shift;
-
- IkiWiki::redirect($cgi, urlto($dest));
- exit;
- }
-sub preprocess (@) {
- #my %params =
- # ( base => ($config{base} ? $config{base} : gettext('Agenda'))
- # , @_ );
- #($form, $buttons) = newevent_form()
- # if not defined $form;
- #my $ret = $form->render();
- return "<div class='newevent'></div>";
- }
-sub sessioncgi ($$) {
- my ($cgi, $session) = @_;
- if (defined $cgi->param('do') && $cgi->param('do') eq "newevent") {
- # TOTRY: decode_cgi_utf8($cgi);
- my $base = Encode::decode_utf8(URI::Escape::uri_unescape(IkiWiki::possibly_foolish_untaint($cgi->param('base'))));
- &IkiWiki::check_canedit($base, $cgi, $session);
- my $page = Encode::decode_utf8(URI::Escape::uri_unescape(IkiWiki::possibly_foolish_untaint($cgi->param('page'))));
-
- my $now_date = DateTime->now
- ( time_zone => 'local'
- , locale => $config{locale}
- )->set_time_zone('floating');
- my %dows = map { ($_ => $now_date->{locale}->day_format_wide->[ $_ ]) } (0..6);
- my %months = map { ($_ => $now_date->{locale}->month_format_wide->[ $_ - 1 ]) } (1..12);
- my $cgi_date;
- eval { $cgi_date = DateTime->new
- ( year => defined $cgi->param("year") ? $cgi->param("year") : $now_date->year()
- , month => defined $cgi->param("month") ? $cgi->param("month") : $now_date->month()
- , day => defined $cgi->param("day") ? $cgi->param("day") : $now_date->day()
- , hour => defined $cgi->param("hour") ? $cgi->param("hour") : $now_date->hour()
- , minute => defined $cgi->param("minute") ? $cgi->param("minute") : $now_date->minute()
- , second => 0
- , nanosecond => 0
- , time_zone => 'local'
- , locale => $config{locale}
- )->set_time_zone('floating') };
- error(sprintf(gettext("illegal date")))
- unless $cgi_date;
-
- my @years = ($cgi_date->year() .. $cgi_date->year()+5);
- my $week_start_day
- = (defined $config{week_start_day} and $config{week_start_day} >= 0 and $config{week_start_day} <= 6)
- ? $config{week_start_day}
- : 1;
- my @dow_order = ($week_start_day .. 6, 0 .. $week_start_day-1);
-
- my $tags = $typedlinks{$page}{tag};
- my $buttons = [qw{Preview Create}];
- my ($from_date, $to_date, $end_date, $inc_dur);
- my $form = CGI::FormBuilder->new
- ( action => IkiWiki::cgiurl()
- , charset => "utf-8"
- , fields => [qw{
- do base
- from_date from_year from_month from_day from_hour from_minute
- to_date to_year to_month to_day to_hour to_minute
- inc_dur inc_year inc_month inc_week inc_day inc_hour inc_minute
- end_times end_date end_year end_month end_day end_hour end_minute
- dom name content
- }]
- , header => 0
- , javascript => 0
- , messages =>
- {
- # form_required_text => 'form_required_text'
- # , form_invalid_text => 'form_invalid_text'
- # , form_invalid_file => 'form_invalid_file'
- # , form_invalid_input => gettext('allowed characters: ').$config{wiki_file_chars}
- form_invalid_select => gettext('invalid selection')
- }
- , method => 'POST'
- , name => "newevent"
- , stylesheet => 1
- , params => $cgi
- , required => [qw{do base year month day name from_date to_date end_date inc_dur}]
- , submit => [qw{Preview Create}]
- , title => gettext("newevent")
- , template => { template("newevent.tmpl") }
- , validate =>
- { from_date => { perl => sub {
- my (undef, $form) = @_;
- $from_date = date_of_form($form, 'from')
- unless defined $from_date;
- defined $from_date
- } }
- , to_date => { perl => sub {
- my (undef, $form) = @_;
- $from_date = date_of_form($form, 'from')
- unless defined $from_date;
- if (defined $from_date) {
- $to_date = date_of_form($form, 'to'
- , year => $from_date->year()
- , month => $from_date->month()
- , day => $from_date->day()
- , hour => $from_date->hour()
- , minute => $from_date->minute());
- defined $to_date
- and (DateTime->compare($from_date, $to_date) <= 0)
- }
- else {return 0;}
- } }
- , end_date => { perl => sub {
- my (undef, $form) = @_;
- if ( $form->field('end_year') ne ''
- or $form->field('end_month') ne ''
- or $form->field('end_day') ne '' ) {
- $from_date = date_of_form($form, 'from')
- unless defined $from_date;
- if (defined $from_date) {
- $end_date = date_of_form($form, 'end'
- , year => $from_date->year()
- , month => $from_date->month()
- , day => $from_date->day()
- , hour => $from_date->hour()
- , minute => $from_date->minute());
- (defined $from_date and defined $end_date
- and DateTime->compare($from_date, $end_date) <= 0)
- }
- else {return 0;}
- }
- else {
- 1;
- }
- } }
- , name => '/^.+$/'
- , base => '/^.*$/'
- , end_times => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
- , inc_year => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
- , inc_month => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
- , inc_week => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
- , inc_day => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
- , inc_hour => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
- , inc_minute => sub { $_[0] =~ m/^\d+$/ and $_[0] >= 0 }
- , inc_dur => sub {
- my (undef, $form) = @_;
- $inc_dur = duration_of_form($form, 'inc');
- defined $inc_dur
- and ($inc_dur->is_positive() or $inc_dur->is_zero());
- }
- }
- );
- $base = $form->field('base') ? $form->field('base') : $base;
- $form->title(sprintf(gettext("creating new events"), pagetitle(IkiWiki::basename($page))));
- $form->field(name => "do", type => "hidden", value => 'newevent', force => 1);
- $form->field(name => "base", type => "hidden", force => 1 , value => $base);
- $form->field(name => "from_date", type => "hidden", value => '1', force => 1);
- $form->field(name => "to_date", type => "hidden", value => '1', force => 1);
- $form->field(name => "end_date", type => "hidden", value => '1', force => 1);
- $form->field(name => "inc_dur", type => "hidden", value => '1', force => 1);
- $form->field(name => "from_year", type => 'select', value => $cgi_date->year(), options => \@years);
- $form->field(name => "from_month", type => 'select'
- , value => sprintf("%02d", $cgi_date->month()).' - '.$months{$cgi_date->month()}
- , options => [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
- $form->field(name => "from_day", type => 'select'
- , value => sprintf("%02d", $cgi_date->day())
- , options => \@days);
- $form->field(name => "from_hour", type => 'select', value => '', options => \@hours);
- $form->field(name => "from_minute", type => 'select', value => '', options => \@minutes);
- $form->field(name => "name", type => 'text', size => 60, value => gettext('New event'));
- $form->field(name => "to_year", type => 'select', value => '', options => \@years);
- $form->field(name => "to_month", type => 'select'
- , value => ''
- , options => [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
- $form->field(name => "to_day", type => 'select'
- , value => ''
- , options => \@days);
- $form->field(name => "to_hour", type => 'select', value => '', options => \@hours);
- $form->field(name => "to_minute", type => 'select', value => '', options => \@minutes);
- $form->field(name => "end_year", type => 'select', value => '', options => \@years);
- $form->field(name => "end_month", type => 'select', value => ''
- , options => [map { sprintf("%02d", $_).' - '.$months{$_} } (1..12)]);
- $form->field(name => "end_day", type => 'select', value => '', options => \@days);
- $form->field(name => "end_hour", type => 'select', value => '', options => \@hours);
- $form->field(name => "end_minute", type => 'select', value => '', options => \@minutes);
- $form->field(name => "end_times", type => 'text', value => '0', size => 2);
- $form->field(name => "inc_year", type => 'text', value => '0', size => 2);
- $form->field(name => "inc_month", type => 'text', value => '0', size => 2);
- $form->field(name => "inc_week", type => 'text', value => '0', size => 2);
- $form->field(name => "inc_day", type => 'text', value => '0', size => 2);
- $form->field(name => "inc_hour", type => 'text', value => '0', size => 2);
- $form->field(name => "inc_minute", type => 'text', value => '0', size => 2);
- my $tmpl_neweventcontent = tmpl($base, 'neweventcontent.tmpl');
- $tmpl_neweventcontent->param(title => gettext('Title of the event'));
- $tmpl_neweventcontent->param(tags => [map {{name => $_}} (sort keys %$tags)]);
- $form->field(name => "content", type => "textarea", size => 30, rows => 20, cols => 80
- , value => $tmpl_neweventcontent->output());
- $form->field(name => "dom", type => 'select', multiple => 1, size => 35
- , options => [map { my $n = $_; map {($n.' '.$dows{$_})} (0..6)} ('1°', '2°', '3°', '4°', '5°')]);
-
- IkiWiki::decode_form_utf8($form);
- IkiWiki::run_hooks(formbuilder_setup => sub {
- shift->(form => $form, cgi => $cgi, session => $session, buttons => $buttons);
- });
- IkiWiki::decode_form_utf8($form);
-
- if (($form->submitted eq 'Create' || $form->submitted eq 'Preview') && $form->validate) {
- #IkiWiki::checksessionexpiry($cgi, $session, $cgi->param('sid'));
- $base
- = $form->field('base')
- ? $form->field('base')
- : (defined $config{base} ? $config{base} : gettext('Agenda'));
- my $end_times
- = $form->field('end_times') == 0
- ? undef : $form->field('end_times');
- my $dom;
- foreach ($form->field('dom')) {
- $dom = {} if not defined $dom;
- $dom->{$_} = 1;
- }
- my $name = $form->field('name');
- $name = IkiWiki::possibly_foolish_untaint(IkiWiki::titlepage($name));
- # NOTE: these untaints are safe because of the checks
- # performed in check_cannewevent later.
- my $content = $form->field('content');
- $content =~ s/\r\n/\n/gs;
- $content =~ s/\n$//s;
-
- # Queue of event creations to perfom.
- my @events = ();
- my $events_try = 0;
- my $events_max
- = defined $config{newevent_max_per_commit}
- ? $config{newevent_max_per_commit} : (2 * 365) ;
- my $pageext = $config{default_pageext};
- while (++$events_try <= $events_max
- and (not defined $end_times or --$end_times >= 0)
- and (not defined $end_date or DateTime->compare($from_date, $end_date) <= 0)) {
- my $dest = page_of_event($form, $from_date, $to_date, $name, $base);
- my $week = $from_date->weekday_of_month();
- my $day = $now_date->{locale}->day_format_wide->[$from_date->day_of_week()-1];
- if (not defined $dom or exists $dom->{"$week° $day"}) {
- push @events,
- { page => $dest
- , file => IkiWiki::newpagefile($dest, $pageext)
- , from => $from_date
- , to => $to_date
- , name => $name
- };
- }
- last unless defined $inc_dur and $inc_dur->is_positive();
- $from_date = $from_date->clone->add_duration($inc_dur);
- $to_date = $to_date->clone->add_duration($inc_dur);
- }
- error("events try per commit overflow: $events_max")
- unless $events_try <= $events_max;
- my $tmpl_neweventpage = tmpl($base, 'neweventpage.tmpl');
- my $i = 0;
- foreach (@events) {
- $tmpl_neweventpage->clear_params();
- $tmpl_neweventpage->param(content => $content);
- $tmpl_neweventpage->param(page => $_->{page});
- $tmpl_neweventpage->param(event => $i);
- $tmpl_neweventpage->param("event_first" => 1)
- if $i == 0;
- $tmpl_neweventpage->param("event_last" => 1)
- if $i == @events - 1;
- $tmpl_neweventpage->param(events => \@events);
- $tmpl_neweventpage->param(from_date => "$_->{from}");
- $tmpl_neweventpage->param(name => $_->{name});
- $tmpl_neweventpage->param(to_date => "$_->{to}");
- $_->{content} = $tmpl_neweventpage->output();
- $i++;
- }
- if ($form->submitted eq 'Create') {
- @events = newevent_hook
- ( cgi => $cgi
- , done => {}
- , events => \@events
- , session => $session
- );
- require IkiWiki::Render;
- if ($config{rcs}) {
- IkiWiki::disable_commit_hook()
- }
- foreach my $event (@events) {
- create($event, $cgi, $session, \%months, $base);
- }
- if ($config{rcs}) {
- IkiWiki::rcs_commit_staged
- ( message => sprintf(gettext("new event"))
- , session => $session );
- IkiWiki::enable_commit_hook();
- IkiWiki::rcs_update();
- }
- IkiWiki::refresh();
- IkiWiki::saveindex();
-
- post_newevent($cgi, $session, (defined $events[0] ? $events[0]->{page} : ''));
- }
- elsif ($form->submitted eq 'Preview') {
- preview($cgi, $session, $form, \@events, \%months);
- IkiWiki::showform($form, $buttons, $session, $cgi);
- }
- }
- else {
- IkiWiki::showform($form, $buttons, $session, $cgi);
- }
-
- exit 0;
- }
- }
-sub preview($$$$) {
- my ($cgi, $session, $form, $events, $months) = @_;
- $form->tmpl_param(year => gettext("year"));
- $form->tmpl_param(month => gettext("month"));
- $form->tmpl_param(day => gettext("day"));
- $form->tmpl_param(hour => gettext("hour"));
- $form->tmpl_param(min => gettext("min"));
- $form->tmpl_param(dow => gettext("day of week"));
- $form->tmpl_param(page => gettext("page"));
- $form->tmpl_param(events => [
- map {
- { from_year => $_->{from}->year()
- , from_month => sprintf('%02d', $_->{from}->month())
- , from_monthname => $months->{$_->{from}->month()}
- , from_day => sprintf('%02d', $_->{from}->day())
- , from_hour => sprintf('%02d', $_->{from}->hour())
- , from_minute => sprintf('%02d', $_->{from}->minute())
- , from_dow => $_->{from}->dow()
- , from_downame => $_->{from}->day_name()
- , to_year => $_->{to}->year()
- , to_month => sprintf('%02d', $_->{to}->month())
- , to_monthname => $months->{$_->{to}->month()}
- , to_day => sprintf('%02d', $_->{to}->day())
- , to_hour => sprintf('%02d', $_->{to}->hour())
- , to_minute => sprintf('%02d', $_->{to}->minute())
- , to_dow => $_->{to}->dow()
- , page =>
- htmllink("", "", $_->{page}
- , linktext => $_->{page}
- , noimageinline => 1)
- }
- } @$events
- ]);
- if (@$events > 0) {
- my $page = @$events[0];
- # FROM: editpage.pm
- my $new = not exists $pagesources{$page};
- # temporarily record its type
- my $type = $config{default_pageext};
- $pagesources{$page} = $page.".".$type if $new;
- my %wasrendered = map { $_ => 1 } @{$renderedfiles{$page}};
- my $content = @$events[0]->{content};
-
- IkiWiki::run_hooks(editcontent => sub {
- $content = shift->
- ( cgi => $cgi
- , content => $content
- , page => $page
- , session => $session
- );
- });
- my $preview = IkiWiki::htmlize($page, $page, $type,
- IkiWiki::linkify($page, $page,
- IkiWiki::preprocess($page, $page,
- IkiWiki::filter($page, $page, $content), 0, 1)));
- IkiWiki::run_hooks(format => sub {
- $preview = shift->
- ( content => $preview
- , page => $page
- );
- });
- $form->tmpl_param("preview", $preview);
-
- # Previewing may have created files on disk.
- # Keep a list of these to be deleted later.
- my %previews = map { $_ => 1 } @{$wikistate{editpage}{previews}};
- foreach my $f (@{$renderedfiles{$page}}) {
- $previews{$f} = 1 unless $wasrendered{$f};
- }
-
- # Throw out any other state changes made during previewing,
- # and save the previews list.
- IkiWiki::loadindex();
- @{$wikistate{editpage}{previews}} = keys %previews;
- IkiWiki::saveindex();
- }
- else {
- $form->tmpl_param("preview", gettext("No event"));
- }
- }
-sub create ($$$$$) {
- my ($event, $cgi, $session, $months, $base) = @_;
- check_cannewevent
- ( $event->{page}
- , $event->{file}
- , $cgi
- , $session
- );
- my $pageext = $config{default_pageext};
-
- $config{cgi} = 0; # NOTE: avoid CGI error message
- eval { writefile($event->{file}, $config{srcdir}, $event->{content}) };
- if ($config{rcs}) {
- IkiWiki::rcs_add($event->{file});
- }
- # month page
- my $monthpage =
- ( $base
- . ($base?'/':'').$event->{from}->year()
- . '/'.sprintf('%02d', $event->{from}->month())
- );
- my $monthfile = IkiWiki::newpagefile($monthpage, $pageext);
- if (not exists $pagesources{$monthpage}
- and not -l $config{srcdir}.'/'.$monthfile
- and not -e _) {
- my $tmpl_neweventmonth = tmpl($base, 'neweventmonth.tmpl');
- $tmpl_neweventmonth->param(base => $base);
- $tmpl_neweventmonth->param(year => $event->{from}->year());
- $tmpl_neweventmonth->param(month => sprintf('%02d', $event->{from}->month()));
- $tmpl_neweventmonth->param(monthname => $months->{$event->{from}->month()});
- my $content = $tmpl_neweventmonth->output();
- eval { writefile($monthfile, $config{srcdir}, $content) };
- if ($config{rcs}) {
- IkiWiki::rcs_add($monthfile);
- }
- }
- # day page
- my $daypage =
- ( $monthpage
- . '/'.sprintf('%02d', $event->{from}->day())
- );
- my $dayfile = IkiWiki::newpagefile($daypage, $pageext);
- if (not exists $pagesources{$daypage}
- and not -l $config{srcdir}.'/'.$dayfile
- and not -e _) {
- my $tmpl_neweventday = tmpl($base, 'neweventday.tmpl');
- $tmpl_neweventday->param(base => $base);
- $tmpl_neweventday->param(year => $event->{from}->year());
- $tmpl_neweventday->param(month => sprintf('%02d', $event->{from}->month()));
- $tmpl_neweventday->param(monthname => $months->{$event->{from}->month()});
- $tmpl_neweventday->param(day => sprintf('%02d', $event->{from}->day()));
- $tmpl_neweventday->param(dayname => $event->{from}->day_name());
- my $content = $tmpl_neweventday->output();
- eval { writefile($dayfile, $config{srcdir}, $content) };
- if ($config{rcs}) {
- IkiWiki::rcs_add($dayfile);
- }
- }
- $config{cgi} = 1;
- }
-sub newevent_hook {
- my %params = @_;
- my @events = @{$params{events}};
- my %done = %{$params{done}};
- my $cgi = $params{cgi};
- my $session = $params{session};
- return ()
- unless @events;
- my @next;
- foreach my $event (@events) {
- unless (exists $done{$event->{page}} && $done{$event->{file}}) {
- IkiWiki::run_hooks(newevent => sub {
- push @next, shift->
- ( cgi => $cgi
- , event => $event
- , session => $session
- );
- });
- $done{$event->{page}} = 1;
- }
- }
- push @events, newevent_hook
- ( cgi => $cgi
- , done => \%done
- , events => \@next
- , session => $session
- );
- my %seen; # NOTE: insure unicity
- return grep { ! $seen{$_->{page}}++ } @events;
- }
-
-1;